package com.ld.shieldsb.common.core.encryptor;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Security;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 密钥长度128，算法模式、补码方式见ALGORITHM,偏移量见ivParameters，加密结果编码方式base64<br/>
 * Utility class providing symmetric AES encryption/decryption. To strengthen the encrypted result, use the {@link #setKey} method to
 * provide a custom key prior to invoking the {@link #encrypt} or {@link #decrypt} methods.
 *
 */
public class AesEncryptor implements Encryptor {

    private static final Logger log = LoggerFactory.getLogger(AesEncryptor.class);
    private static final String ALGORITHM = "AES/GCM/NoPadding"; // AES/CBC/PKCS7Padding are vulnerable because they don't provide a HMAC:-
                                                                 // CBC - OFB - CTR - ECB

    private static final byte[] DEFAULT_IV_PARAM = "315a bcde fghi jk11".replaceAll(" ", "").getBytes(); // 默认偏移量16位
    private static final byte[] DEFAULT_KEY = "315a bcde fghi jk11".replaceAll(" ", "").getBytes(); // 16位，默认密码，可在具体对象中设置

    private byte[] cipherKey = null; // 密码，取前16位，不足16位取默认密码补齐
    private byte[] ivParameters = null; // 偏移量，取前16位，不足16位取默认密码补齐
    private static boolean isInitialized = false;

    static {
//        new SecureRandom().nextBytes(initParm);
    }

    public static void main(String[] args) {
        try {
            System.out.println("abcdefghijkaabbc".getBytes("UTF-8").length);
        } catch (UnsupportedEncodingException e) {
        }

    }

    /** Default constructor */
    public AesEncryptor() {
        initialize();
    }

    /** Custom key constructor */
    public AesEncryptor(String key) {
        initialize();
        setKey(key);
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.util.Encryptor#encrypt(java.lang.String)
     */
    @Override
    public String encrypt(String value) {
        if (value == null) {
            return null;
        }
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        Encoder encoder = Base64.getEncoder();
        String outstr = encoder.encodeToString(cipher(bytes, getKey(), Cipher.ENCRYPT_MODE));
        return outstr;
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.util.Encryptor#decrypt(java.lang.String)
     */
    @Override
    public String decrypt(String value) {
        if (value == null) {
            return null;
        }
        Decoder decoder = Base64.getDecoder();

        byte[] bytes = cipher(decoder.decode(value), getKey(), Cipher.DECRYPT_MODE);
        if (bytes == null) {
            return null;
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * Symmetric encrypt/decrypt routine.
     *
     * @param attribute
     *            The value to be converted
     * @param key
     *            The encryption key
     * @param mode
     *            The cipher mode (encrypt or decrypt)
     * @return The converted attribute, or null if conversion fails
     */
    private byte[] cipher(byte[] attribute, byte[] key, int mode) {
        byte[] result = null;
        try {
            // Create AES encryption key
            Key aesKey = new SecretKeySpec(key, "AES");

            // Create AES Cipher
            Cipher aesCipher = Cipher.getInstance(ALGORITHM);

//            byte[] iv = new byte[16];
//            new SecureRandom().nextBytes(iv);
            // Initialize AES Cipher and convert
            aesCipher.init(mode, aesKey, new IvParameterSpec(getIvParameters()));
            result = aesCipher.doFinal(attribute);
        } catch (Exception e) {
            log.error("AES cipher failed", e);
        }
        return result;
    }

    /**
     * Return the encryption key. This will return the user-defined key (if available) or a default encryption key.
     *
     * @return The encryption key
     */
    private byte[] getKey() {
        return cipherKey == null ? DEFAULT_KEY : cipherKey;
    }

    private byte[] getIvParameters() {
        return ivParameters == null ? DEFAULT_IV_PARAM : ivParameters;
    }

    /**
     * Set the encryption key. This will apply the user-defined key, truncated or filled (via the default key) as needed to meet the key
     * length specifications.
     *
     * @param key
     *            The encryption key
     */
    private void setKey(byte[] key) {
        cipherKey = editKey(key);
    }

    private void setIvParameters(byte[] key) {
        cipherKey = editKey(key);
    }

    /**
     * Validates an optional user-defined encryption key. Only the first sixteen bytes of the input array will be used for the key. It will
     * be filled (if necessary) to a minimum length of sixteen.
     *
     * @param key
     *            The user-defined encryption key
     * @return A valid encryption key, or null
     */
    private byte[] editKey(byte[] key) {
        if (key == null) {
            return null;
        }
        byte[] result = new byte[DEFAULT_KEY.length];
        for (int x = 0; x < DEFAULT_KEY.length; x++) {
            result[x] = x < key.length ? key[x] : DEFAULT_KEY[x];
        }
        return result;
    }

    /** Installs the required security provider(s) */
    private synchronized void initialize() {
        if (!isInitialized) {
            try {
                Security.addProvider(new BouncyCastleProvider());
                isInitialized = true;
            } catch (Throwable t) {
                log.warn("JCE provider failure; unable to load BC", t);
            }
        }
    }

    @Override
    public void setKey(String key) {
        if (key == null) {
            cipherKey = null;
            return;
        }
        byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
        setKey(editKey(bytes));
    }

    public void setIvParameters(String key) {
        if (key == null) {
            ivParameters = null;
            return;
        }
        byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
        setIvParameters(editKey(bytes));
    }

    @Override
    public boolean match(String plaintext, String hashed, String salt) {
        if (plaintext == null || hashed == null) {
            return false;
        }
        return decrypt(hashed).equals(plaintext);
    }

}
